import java.util.Map;
import java.util.Map.Entry;
import java.util.HashMap;

import org.biojava3.core.exceptions.CompoundNotFoundError;
import org.biojava3.core.sequence.compound.NucleotideCompound;

public class Site {
	private NucleotideCompound lastOne;
	private int change;
	private double adenine = 0;
	private double guanine = 0;
	private double cytosine = 0;
	private double uracil = 0;
	private double gaps = 0;
	private boolean distributionIsSet = false;
	private boolean entropyIsSet = false;
	private double entropy = 0;
	private int sequences = 0;
	
	private Map<NucleotideCompound, Integer> siteInfo;
	
	/*
	 * Constructor
	 */
	public Site () {
		siteInfo = new HashMap<NucleotideCompound, Integer>();
		change = 0;
	}
	/*
	 * Returns a map of nucleotide compounds present at this site
	 */
	public Map<NucleotideCompound, Integer> getSiteInfo() {
		return siteInfo;
	}
	/*
	 * Associates the specified integer value with the spacified nucleotide compound
	 */
	public void addCompound(NucleotideCompound currentCompound, Integer currentInteger) {
		siteInfo.put(currentCompound, currentInteger);
	}
	/*
	 * Returns the integer value to which the specified nucleotide compound is mapped at this site
	 */
	public Integer getIntegerValue(NucleotideCompound currentCompound) {
		if (!siteInfo.containsKey(currentCompound))
			return 0;
		return siteInfo.get(currentCompound);
	}
	/*
	 * Register the nucleotide compound added to the site
	 */
	public void setlastOne(NucleotideCompound lastCompound) {
		lastOne = lastCompound;
	}
	/*
	 * Get the last nucleotide added to the site
	 */
	public NucleotideCompound getLastOne() {
		return lastOne;
	}
	/*
	 * Register nucleotide change at the site
	 */
	public void increaseChange() {
		change++;
	}
	/*
	 * Get the number of changes at the site
	 */
	public int getChange() {
		return change;
	}
	/*
	 * Calculate total number of sequences
	 */
	public void numberOfSequences() {
		for(Entry<NucleotideCompound, Integer> entry : siteInfo.entrySet())
			sequences += entry.getValue();
	}
	/*
	 * Get the most frequent nucleotide
	 */
	public NucleotideCompound getMostFrequentNucleotide() {
		NucleotideCompound mostFrequent = null;
		int frequency = 0;
		for(Entry<NucleotideCompound, Integer> entry : siteInfo.entrySet()) {
			if (!isGap(entry.getKey()) && entry.getValue() > frequency) {
				frequency = entry.getValue();
				mostFrequent = entry.getKey();
			}
		}
		return mostFrequent;
	}
	/*
	 * get second most frequent nucleotide
	 */
	public NucleotideCompound getSecondMostFrequentNucleotide() {
		NucleotideCompound mostFrequent = getMostFrequentNucleotide();
		NucleotideCompound secondMostFrequent = null;
		int frequency = 0;
		for(Entry<NucleotideCompound, Integer> entry : siteInfo.entrySet()) {
			if ((entry.getValue() > frequency) && !entry.getKey().equals(mostFrequent)) {
				if (!isGap(entry.getKey())) {
					frequency = entry.getValue();
					secondMostFrequent = entry.getKey();
				}
			}
		}
		return secondMostFrequent;
	}
	/*
	 * get third most frequent nucleotide
	 */
	public NucleotideCompound getThirdMostFrequentNucleotide() {
		NucleotideCompound mostFrequent = getMostFrequentNucleotide();
		NucleotideCompound secondMostFrequent = getSecondMostFrequentNucleotide();
		NucleotideCompound thirdMostFrequent = null;
		int frequency = 0;
		for (Entry<NucleotideCompound, Integer> entry : siteInfo.entrySet()) {
			if ((entry.getValue() > frequency) && !entry.getKey().equals(mostFrequent) && !entry.getKey().equals(secondMostFrequent)) {
				if (!isGap(entry.getKey())) {
					frequency = entry.getValue();
					thirdMostFrequent = entry.getKey();
				}
			}
		}
		return thirdMostFrequent;
	}
	/*
	 * Print siteInfo
	 */
	public void printSiteInfo() {
		for (Entry<NucleotideCompound, Integer> entry : siteInfo.entrySet()) {
			System.out.print(entry.getKey().getBase() + ": " + entry.getValue() + ", ");
		}
		System.out.println();
	}
	/*
	 * Set nucleotide distribution for the Site
	 */
	public void setDistribution() {
		int ade = 0;
		int gua = 0;
		int cyt = 0;
		int ura = 0;
		int gap = 0;
		int sum = 0;
		for (Entry<NucleotideCompound, Integer> entry : siteInfo.entrySet()) {
			if ((entry.getKey().getBase().equals(".")) || (entry.getKey().getBase().equals("-")))
				gap += entry.getValue();
			else if (entry.getKey().getBase().equals("A")) 
				ade += entry.getValue();
			else if (entry.getKey().getBase().equals("G"))
				gua += entry.getValue();
			else if (entry.getKey().getBase().equals("C"))
				cyt += entry.getValue();
			else if (entry.getKey().getBase().equals("U"))
				ura += entry.getValue();
			else if (entry.getKey().getBase().equals("M")) {
				ade += entry.getValue();
				cyt += entry.getValue();
			}
			else if(entry.getKey().getBase().equals("R")) {
				ade += entry.getValue();
				gua += entry.getValue();
			}
			else if(entry.getKey().getBase().equals("W")) {
				ade += entry.getValue();
				ura += entry.getValue();
			}
			else if(entry.getKey().getBase().equals("S")) {
				cyt += entry.getValue();
				gua += entry.getValue();
			}
			else if(entry.getKey().getBase().equals("Y")) {
				cyt += entry.getValue();
				gua += entry.getValue();
			}
			else if(entry.getKey().getBase().equals("K")) {
				gua += entry.getValue();
				ura += entry.getValue();
			}
			else if(entry.getKey().getBase().equals("V")) {
				ade += entry.getValue();
				cyt += entry.getValue();
				gua += entry.getValue();
			}
			else if(entry.getKey().getBase().equals("H")) {
				ade += entry.getValue();
				cyt += entry.getValue();
				ura += entry.getValue();
			}
			else if(entry.getKey().getBase().equals("D")) {
				ade += entry.getValue();
				gua += entry.getValue();
				ura += entry.getValue();
			}
			else if(entry.getKey().getBase().equals("B")) {
				cyt += entry.getValue();
				gua += entry.getValue();
				ura += entry.getValue();
			}
			else if(entry.getKey().getBase().equals("N")) {
				gua += entry.getValue();
				ade += entry.getValue();
				ura += entry.getValue();
				cyt += entry.getValue();
			}
			else
				throw new CompoundNotFoundError(entry.getKey().getBase());
		}
		sum = ade + gua + cyt + ura;
		adenine = (double)ade/sum;
		guanine = (double)gua/sum;
		cytosine = (double)cyt/sum;
		uracil = (double)ura/sum;
		if (sequences == 0)
			numberOfSequences();
		gaps = (double)gap/sequences;
		distributionIsSet = true;
	}
	/*
	 * Get relative abundance for the Nucleotide
	 */
	public double getRelativeAbundance(NucleotideCompound currentNucleotide) {
		if (sequences == 0)
			numberOfSequences();
		if (!distributionIsSet)
			setDistribution();
		if(currentNucleotide.getBase().equals("A"))
			return adenine;
		else if(currentNucleotide.getBase().equals("G"))
			return guanine;
		else if(currentNucleotide.getBase().equals("C"))
			return cytosine;
		else if(currentNucleotide.getBase().equals("U"))
			return uracil;
		else if((currentNucleotide.getBase().equals("-")) || (currentNucleotide.getBase().equals(".")))
			return gaps;
		else if (currentNucleotide.getBase().equals("M"))
			return (double)siteInfo.get(currentNucleotide)/sequences;
		else if (currentNucleotide.getBase().equals("R"))
			return (double)siteInfo.get(currentNucleotide)/sequences;
		else if (currentNucleotide.getBase().equals("W"))
			return (double)siteInfo.get(currentNucleotide)/sequences;
		else if (currentNucleotide.getBase().equals("S"))
			return (double)siteInfo.get(currentNucleotide)/sequences;
		else if (currentNucleotide.getBase().equals("Y"))
			return (double)siteInfo.get(currentNucleotide)/sequences;
		else if (currentNucleotide.getBase().equals("K"))
			return (double)siteInfo.get(currentNucleotide)/sequences;
		else if (currentNucleotide.getBase().equals("V"))
			return (double)siteInfo.get(currentNucleotide)/sequences;
		else if (currentNucleotide.getBase().equals("H"))
			return (double)siteInfo.get(currentNucleotide)/sequences;
		else if (currentNucleotide.getBase().equals("D"))
			return (double)siteInfo.get(currentNucleotide)/sequences;
		else if (currentNucleotide.getBase().equals("B"))
			return (double)siteInfo.get(currentNucleotide)/sequences;
		else if (currentNucleotide.getBase().equals("N"))
			return (double)siteInfo.get(currentNucleotide)/sequences;
		else
			throw new CompoundNotFoundError(currentNucleotide.getBase());
	}
	/*
	 * Set Shannon entropy for the Site
	 */
	public void setEntropy() {
		double probAde;
		double probGua;
		double probCyt;
		double probUra;
//		double probGap;
		if (!distributionIsSet)
			setDistribution();
		if (adenine == 0)
			probAde = 0;
		else
			probAde = adenine*Math.log(adenine)/Math.log(2);
		if (guanine == 0)
			probGua = 0;
		else
			probGua = guanine*Math.log(guanine)/Math.log(2);
		if (cytosine == 0)
			probCyt = 0;
		else
			probCyt = cytosine*Math.log(cytosine)/Math.log(2);
		if (uracil == 0)
			probUra = 0;
		else
			probUra = uracil*Math.log(uracil)/Math.log(2);
//		if (gaps == 0)
//			probGap = 0;
//		else
//			probGap = gaps*Math.log(gaps)/Math.log(2);
		entropy = -probAde - probGua - probCyt - probUra;// - probGap;
		entropyIsSet = true;
	}
	/*
	 * Get Shannon entropy for the Site
	 */
	public double getEntropy() {
		if(!entropyIsSet)
			setEntropy();
		return entropy;
	}
	/*
	 * Get relative abundance of gap
	 */
	public double getGaps() {
		if (!distributionIsSet)
			setDistribution();
		return gaps;
	}
	/*
	 * Return true if the nucleotide is a gap
	 */
	public boolean isGap(NucleotideCompound currentNucleotide) {
		if ((currentNucleotide.getBase().equals(".")) || (currentNucleotide.getBase().equals("-")))
			return true;
		else
			return false;
	}
}